package com.andengine.editor.textureregioneditor;

import com.andengine.editor.atlaseditor.Atlas;
import com.andengine.editor.data.TextureFile;
import com.andengine.editor.factories.AtlasFactory;
import com.andengine.editor.factories.TextureRegionFactory;
import com.gooddies.ballontips.ToolTipHandler;
import com.gooddies.swing.HSlider;
import com.gooddies.swing.Toast;
import com.gooddies.swing.hComboBox;
import com.gooddies.texteditors.DefaultTextField;
import com.swingson.Swingson;
import com.swingson.Swingson.UserInputEnabler;
import com.swingson.SwingsonGuiBuilder;
import com.swingson.exceptions.ErrorMessageException;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Timer;

public class TextureRegionEditorPanel extends JPanel {

    private final hComboBox<AtlasShort> atlassCB = new hComboBox<AtlasShort>();
    private final hComboBox<TextureFileWithThumbnail> texturesCB = new hComboBox<TextureFileWithThumbnail>();
    private final hComboBox<TextureAnimationData> textureRegionAnimationDataCB = new hComboBox<TextureAnimationData>();
    private final hComboBox<AnimationType> animationTypeCB = new hComboBox<AnimationType>(AnimationType.values());
    private final JScrollPane previewTextureScroll = new JScrollPane();
    private final PreviewPanel previewPanel = new PreviewPanel();
    private final NumberSpinner columnCountSpinner = new NumberSpinner();
    private final NumberSpinner rowCountSpinner = new NumberSpinner();
    private final HSlider frameSecuenceSlider = new HSlider();
    private final NumberSpinner currentFrameSpinner = new NumberSpinner();
    private final NumberSpinner minFrameSpinner = new NumberSpinner();
    private final NumberSpinner maxFrameSpinner = new NumberSpinner();
    private final NumberSpinner speedSpinner = new NumberSpinner();
    private final DefaultTextField animationNameField = new DefaultTextField(40);
    private Timer timer;
    private final JPanel tilePreviewPanel = new JPanel() {

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            tilePanelDraw(g);
        }
    };

    private TextureAnimationData createNewSpriteAnimationData() {
        TextureAnimationData animData = new TextureAnimationData();
        animData.setAnimationType(AnimationType.LOOP);
        animData.setFrameRate(30);
        animData.setMaxFrame((int) rowCountSpinner.getValue() + (int) columnCountSpinner.getValue() - 1);

        int index = 0;
        while (true) {
            String name = "default" + (index == 0 ? "" : index);
            if (getAnimationDataByName(name) == null) {
                animData.setTrackName(name);
                break;
            }
            index++;
        }

        return animData;
    }

    private TextureAnimationData getAnimationDataByName(String name) {
        name = name.trim();
        for (TextureAnimationData data : textureRegionAnimationDataCB) {
            if (data.getTrackName().equals(name)) {
                return data;
            }
        }
        return null;
    }

    private TextureAnimationData getCurrentSpriteAnimationData() {
        if (textureRegionAnimationDataCB.getSelectedItem() == null) {
            try (UserInputEnabler e = Swingson.disableUserInput()) {
                TextureAnimationData animData = createNewSpriteAnimationData();
                textureRegionAnimationDataCB.addItem(animData);
                textureRegionAnimationDataCB.setSelectedIndex(0);
                return animData;
            }
        }

        return textureRegionAnimationDataCB.getSelectedItem();
    }

    private void loadSpriteAnimationData(TextureAnimationData data) {
        stopAnimation();
        try (UserInputEnabler e = Swingson.disableUserInput()) {
            speedSpinner.setValue(data.getFrameRate());
            animationTypeCB.setSelectedItem(data.getAnimationType());
            setMinFrame(data.getMinFrame());
            setCurrentFrame(data.getMinFrame());
            setMaxFrame(data.getMaxFrame());
            animationNameField.setText(data.getTrackName());
        }
    }

    public TextureRegionEditorPanel() {
        setPreferredSize(new Dimension(800, 600));
        loadAtlassesList();
        initTexturesCheckBox();
        initGui();
        loadSpriteAnimationData(getCurrentSpriteAnimationData());
    }

    private void initGui() {
        SwingsonGuiBuilder.createGuiFromJsonInPackage("spriteEditor.json", this);
        frameSecuenceSlider.setOnChangeEvent(new HSlider.ChangeEvent() {
            @Override
            public void changeValue(HSlider.SelectedMark mark, int value) {
                if (Swingson.isEnabledUserInput()) {
                    onAnimationSliderChange(mark, value);
                }
            }
        });
    }

    private void initTexturesCheckBox() {
        texturesCB.setRenderer(new TexturesListCellRenderer());
    }

    private AtlasShort getSelectedAtlas() {
        return atlassCB.getSelectedItem();
    }

    private void loadAtlassesList() {
        try (UserInputEnabler enabler = Swingson.disableUserInput()) {
            List<AtlasShort> atlassesNames = AtlasFactory.get().getAtlasesNames();
            atlassCB.clearItems();
            for (AtlasShort atlas : atlassesNames) {

                atlassCB.addItem(atlas);
            }

            if (atlassCB.getItemCount() > 0) {
                atlassCB.setSelectedIndex(0);
            }
        }

        loadTexturesList();
        if (!isTextureListEmpty()) {
            selectTexture(getSelectedTexture());
        }
    }

    private boolean isTextureListEmpty() {
        return texturesCB.getItemCount() == 0;
    }

    private TextureFile getSelectedTexture() {
        TextureFileWithThumbnail f = texturesCB.getSelectedItem();
        if (f != null) {
            return f.getTextureFile();
        }
        return null;
    }

    private void loadTexturesList() {
        try (UserInputEnabler enabler = Swingson.disableUserInput()) {
            if (getSelectedAtlas() != null) {
                String atlasName = getSelectedAtlas().getName();
                if (atlasName != null) {
                    Atlas atlas = AtlasFactory.get().read(atlasName);
                    texturesCB.clearItems();
                    for (TextureFile texture : atlas.getTextureFiles()) {
                        texturesCB.addItem(new TextureFileWithThumbnail(texture));
                    }
                }
            }
        }
    }

    protected void onPreviewPanelResized() {
        stopAnimation();
        previewPanel.recalculateOffsets();
        previewPanel.repaint();
    }

    protected void onSelectTextureFromCb(TextureFileWithThumbnail file) {
        stopAnimation();
        selectTexture(file.getTextureFile());
    }

    protected void onAnimationNameChange() {
        String animationName = animationNameField.getText();
        if (animationName.trim().isEmpty()) {
            throw new ErrorMessageException("Track name should not be empty", animationNameField);
        }

        TextureAnimationData duplicateNameAnimData = getAnimationDataByName(animationName);
        if (duplicateNameAnimData != null && duplicateNameAnimData != getCurrentSpriteAnimationData()) {
            throw new ErrorMessageException("Such track name already exists", animationNameField);
        }
        getCurrentSpriteAnimationData().setTrackName(animationName);
        textureRegionAnimationDataCB.repaint();
    }

    private void selectTexture(TextureFile textureFile) {
        try {
            BufferedImage previewImage = ImageIO.read(textureFile);
            previewPanel.setImage(previewImage);
            previewTextureScroll.revalidate();
            previewTextureScroll.validate();
            previewTextureScroll.repaint();
            tilePreviewPanel.repaint();
        } catch (IOException ex) {
            throw new ErrorMessageException(ex.getMessage());
        }
    }

    private void onAnimationSliderChange(HSlider.SelectedMark mark, int value) {
        stopAnimation();
        try (UserInputEnabler e = Swingson.disableUserInput()) {
            if (mark == HSlider.SelectedMark.Value) {
                setCurrentFrame(value);
            } else if (mark == HSlider.SelectedMark.LeftBoundary) {
                setMinFrame(value);
            } else if (mark == HSlider.SelectedMark.RightBoundary) {
                setMaxFrame(value);
            }
        }
    }

    private void setCurrentFrame(int frame) {
        frameSecuenceSlider.setCurrent(frame);
        currentFrameSpinner.setValue(frame);
        tilePreviewPanel.repaint();
    }

    private void setMinFrame(int frame) {
        frameSecuenceSlider.setMinBoundary(frame);
        minFrameSpinner.setValue(frame);
        tilePreviewPanel.repaint();
    }

    private void setMaxFrame(int frame) {
        frameSecuenceSlider.setMaxBoundary(frame);
        maxFrameSpinner.setValue(frame);
        tilePreviewPanel.repaint();
    }

    private void columnNumberChanged() {
        stopAnimation();
        previewPanel.setColumnCount((int) columnCountSpinner.getValue());
        updateFrameFields();
        previewPanel.repaint();
        tilePreviewPanel.repaint();
    }

    private void rowNumberChanged() {
        stopAnimation();
        previewPanel.setRowCount((int) rowCountSpinner.getValue());
        updateFrameFields();
        previewPanel.repaint();
        tilePreviewPanel.repaint();
    }

    private int getCurrentFrame() {
        return (int) currentFrameSpinner.getValue();
    }

    private void updateFrameFields() {
        try (UserInputEnabler enabler = Swingson.disableUserInput()) {
            maxFrameSpinner.setValue(previewPanel.getFrameCount() - 1);
            maxFrameSpinner.setMax(previewPanel.getFrameCount() - 1);
            maxFrameSpinner.setMin(0);

            currentFrameSpinner.setMin(0);
            currentFrameSpinner.setMax(previewPanel.getFrameCount() - 1);
            currentFrameSpinner.setValue(0);

            minFrameSpinner.setMin(0);
            minFrameSpinner.setMax(previewPanel.getFrameCount() - 1);
            minFrameSpinner.setCurrent(0);

            frameSecuenceSlider.setMinBoundary(0);
            frameSecuenceSlider.setMinimum(0);
            frameSecuenceSlider.setMaxBoundary(previewPanel.getFrameCount() - 1);
            frameSecuenceSlider.setMaximum(previewPanel.getFrameCount() - 1);
        }
    }

    private void manualChangeCurrentFrame() {
        stopAnimation();
        frameSecuenceSlider.setCurrent(getCurrentFrame());
        tilePreviewPanel.repaint();
    }

    private AnimationType getAnimationType() {
        return animationTypeCB.getSelectedItem();
    }

    private void manualAnimationTypeChange() {
        stopAnimation();
        getCurrentSpriteAnimationData().setAnimationType(animationTypeCB.getSelectedItem());
    }

    private void manualChangeMaxFrame() {
        stopAnimation();
        int maxFrame = (int) maxFrameSpinner.getValue();
        getCurrentSpriteAnimationData().setMaxFrame(maxFrame);
        frameSecuenceSlider.setMaxBoundary(maxFrame);
        tilePreviewPanel.repaint();
    }

    private void manualChangeSpeed() {
        getCurrentSpriteAnimationData().setFrameRate((int) speedSpinner.getValue());
        stopAnimation();
        startAnimation();
    }

    private void manualChangeMinFrame() {
        stopAnimation();
        frameSecuenceSlider.setMinBoundary((int) minFrameSpinner.getValue());
        getCurrentSpriteAnimationData().setMinFrame((int) minFrameSpinner.getValue());
        tilePreviewPanel.repaint();
    }

    private void tilePanelDraw(Graphics g) {
        g.setColor(Color.BLACK);
        g.drawRect(0, 0, tilePreviewPanel.getWidth() - 1, tilePreviewPanel.getHeight() - 1);
        previewPanel.drawTile(g, 0, 0, tilePreviewPanel.getWidth(), tilePreviewPanel.getHeight(), getCurrentFrame());
    }

    private int getFrameCount() {
        return (int) maxFrameSpinner.getValue() - (int) minFrameSpinner.getValue();
    }

    private int getFrameRate() {
        return (int) speedSpinner.getValue();
    }

    private int convertFrameRateToDelay(int frameRate) {
        return 1000 / frameRate;
    }

    private void startAnimation() {
        if (getFrameCount() > 0 && getAnimationType() != AnimationType.NO) {
            if (timer == null) {
                timer = new Timer(convertFrameRateToDelay(getFrameRate()), new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        onNextFrameInAnimation();
                    }
                });

                timer.setCoalesce(true);
                timer.setRepeats(true);
            }

            if (!timer.isRunning()) {
                timer.setDelay(convertFrameRateToDelay(getFrameRate()));
                timer.restart();
            }
        }
    }

    private void onNextFrameInAnimation() {
        int frame = getCurrentFrame();
        frame++;
        if (frame >= ((int) maxFrameSpinner.getValue() + 1)) {
            if (getAnimationType() == AnimationType.ONESHOT) {
                stopAnimation();
                frame = (int) maxFrameSpinner.getValue();
            } else {
                frame = (int) minFrameSpinner.getValue();
            }
        }
        try (UserInputEnabler e = Swingson.disableUserInput()) {
            setCurrentFrame(frame);
        }
    }

    private void stopAnimation() {
        if (timer != null) {
            if (timer.isRunning()) {
                timer.stop();
            }
        }
    }

    protected void onError(Throwable ex) {
        if (ex instanceof ErrorMessageException) {
            ErrorMessageException e = (ErrorMessageException) ex;
            if (e.getComponent() != null) {
                ToolTipHandler.error(e.getComponent(), e.getMessage());
            } else {
                Toast.makeText(this, ex.getMessage(), Toast.Style.ERROR).display();
            }
        } else {
            throw new RuntimeException(ex);
        }
    }

    protected void onAddAnimation() {
        TextureAnimationData animationData = createNewSpriteAnimationData();
        textureRegionAnimationDataCB.addItem(animationData);
        textureRegionAnimationDataCB.setSelectedItem(animationData);
    }

    protected void onRemoveAnimation() {
        if (textureRegionAnimationDataCB.getItemCount() == 1) {
            throw new ErrorMessageException("Cannot remove last animation data", textureRegionAnimationDataCB);
        }

        TextureAnimationData animData = getCurrentSpriteAnimationData();
        textureRegionAnimationDataCB.removeItem(animData);
    }

    protected void onSpriteAnimationDataSelected(TextureAnimationData animationData) {
        try (UserInputEnabler enabler = Swingson.disableUserInput()) {
            setCurrentFrame(animationData.getMinFrame());
            setMaxFrame(animationData.getMaxFrame());
            setMinFrame(animationData.getMinFrame());
            speedSpinner.setValue(animationData.getFrameRate());
            animationNameField.setText(animationData.getTrackName());
            animationTypeCB.setSelectedItem(animationData.getAnimationType());
        }
    }

    protected void onSave() {
        TextureRegion sprite = new TextureRegion();
        sprite.setTextureName(texturesCB.getSelectedItem().getTextureFile().getSmallPath());
        sprite.setAnimationData(textureRegionAnimationDataCB.getItemsAsList());
        sprite.setRowCount((int) rowCountSpinner.getValue());
        sprite.setColumnCount((int) columnCountSpinner.getValue());
        sprite.setAtlasId(getSelectedAtlas().getId());
        TextureRegionFactory.get().save("mysprite", sprite);
    }

    protected void onLoad() {
        TextureRegion sprite = TextureRegionFactory.get().read("mysprite");
        for (AtlasShort a : atlassCB) {
            if (a.getId() == sprite.getAtlasId()) {
                atlassCB.setSelectedItem(a);
                break;
            }
        }

        for (TextureFileWithThumbnail texture : texturesCB) {
            if (texture.getTextureFile().getSmallPath().equals(sprite.getTextureName())) {
                texturesCB.setSelectedItem(texture);
                break;
            }
        }

        rowCountSpinner.setValue(sprite.getRowCount());
        columnCountSpinner.setValue(sprite.getColumnCount());

        try (UserInputEnabler input = Swingson.disableUserInput()) {
            textureRegionAnimationDataCB.clearItems();
            for (TextureAnimationData s : sprite.getAnimationData()) {
                textureRegionAnimationDataCB.addItem(s);
            }
        }

        textureRegionAnimationDataCB.setSelectedIndex(0);
        onSpriteAnimationDataSelected(textureRegionAnimationDataCB.getSelectedItem());
    }
}
